Overview

Use linked DMA channels to perform "scan" across multiple ADC input channels.

See diagram below.

Channel configuration

  • DMA channel $i$ copies conesecutive SC1A configurations to the ADC SC1A register. Each SC1A configuration selects an analog input channel.
    • Channel $i$ is initially triggered by software trigger (i.e., DMA_SSRT = i), starting the ADC conversion for the first ADC channel configuration.
    • Loading of subsequent ADC channel configurations is triggered through minor loop linking of DMA channel $ii$ to DMA channel $i$.
  • DMA channel $ii$ is triggered by ADC conversion complete (i.e., COCO), and copies the output result of the ADC to consecutive locations in the result array.
    • Channel $ii$ has minor loop link set to channel $i$, which triggers the loading of the next channel SC1A configuration to be loaded immediately after the current ADC result has been copied to the result array.
  • After $n$ triggers of channel $i$, the result array contains $n$ ADC results, one result per channel in the SC1A table.

Device

Connect to device


In [1]:
from arduino_rpc.protobuf import resolve_field_values
from teensy_minimal_rpc import SerialProxy
import teensy_minimal_rpc.DMA as DMA
import teensy_minimal_rpc.ADC as ADC


# Disconnect from existing proxy (if available)
try:
    del proxy
except NameError:
    pass

proxy = SerialProxy()

Configure ADC sample rate, etc.


In [2]:
import arduino_helpers.hardware.teensy as teensy

# Set ADC parameters
proxy.setAveraging(16, teensy.ADC_0)
proxy.setResolution(16, teensy.ADC_0)
proxy.setConversionSpeed(teensy.ADC_MED_SPEED, teensy.ADC_0)
proxy.setSamplingSpeed(teensy.ADC_MED_SPEED, teensy.ADC_0)
proxy.update_adc_registers(
    teensy.ADC_0,
    ADC.Registers(CFG2=ADC.R_CFG2(MUXSEL=ADC.R_CFG2.B)))


Out[2]:
0

Pseudo-code to set DMA channel $i$ to be triggered by ADC0 conversion complete.

DMAMUX0_CFGi[SOURCE] = DMAMUX_SOURCE_ADC0  // Route ADC0 as DMA channel source.
DMAMUX0_CFGi[TRIG] = 0  // Disable periodic trigger.
DMAMUX0_CFGi[ENBL] = 1  // Enable the DMAMUX configuration for channel.

DMA_ERQ[i] = 1  // DMA request input signals and this enable request flag
                // must be asserted before a channel’s hardware service
                // request is accepted (21.3.3/394).
DMA_SERQ = i  // Can use memory mapped convenience register to set instead.

Set DMA mux source for channel 0 to ADC0


In [3]:
DMAMUX_SOURCE_ADC0 = 40  # from `kinetis.h`
DMAMUX_SOURCE_ADC1 = 41  # from `kinetis.h`

#    DMAMUX0_CFGi[SOURCE] = DMAMUX_SOURCE_ADC0  // Route ADC0 as DMA channel source.
#    DMAMUX0_CFGi[TRIG] = 0  // Disable periodic trigger.
#    DMAMUX0_CFGi[ENBL] = 1  // Enable the DMAMUX configuration for channel.
proxy.update_dma_mux_chcfg(0, DMA.MUX_CHCFG(SOURCE=DMAMUX_SOURCE_ADC0,
                                            TRIG=False,
                                            ENBL=True))

# DMA request input signals and this enable request flag
# must be asserted before a channel’s hardware service
# request is accepted (21.3.3/394).
#    DMA_SERQ = i
proxy.update_dma_registers(DMA.Registers(SERQ=0))
proxy.enableDMA(teensy.ADC_0)

In [4]:
proxy.DMA_registers().loc['']


Out[4]:
full_name value short_description page
parent_name
ERQ 1 Enable Request Register 21.3.3/394
ERR 1 Error Register 21.3.14/409
INT 0 Interrupt Request Register 21.3.13/406
EEI 0 Enable Error Interrupt Register 21.3.4/397
HRS 0 Hardware Request Status Register 21.3.15/411

In [5]:
dmamux0 = DMA.MUX_CHCFG.FromString(proxy.read_dma_mux_chcfg(0).tostring())
resolve_field_values(dmamux0)[['full_name', 'value']]


Out[5]:
full_name value
parent_name
SOURCE 40
TRIG False
ENBL True

In [6]:
adc0 = ADC.Registers.FromString(proxy.read_adc_registers(teensy.ADC_0).tostring())
resolve_field_values(adc0)[['full_name', 'value']].loc[['CFG2', 'SC1A', 'SC3']]


Out[6]:
full_name value
parent_name
CFG2 CFG2.MUXSEL B
CFG2 CFG2.ADLSTS ADD_6_ADCK_CYCLES
CFG2 CFG2.ADHSC False
CFG2 CFG2.ADACKEN False
SC1A SC1A.COCO False
SC1A SC1A.DIFF False
SC1A SC1A.AIEN False
SC1A SC1A.ADCH 5
SC3 SC3.AVGE True
SC3 SC3.ADCO False
SC3 SC3.AVGS _8
SC3 SC3.CALF False
SC3 SC3.CAL False

Analog channel list

  • List of channels to sample.
  • Map channels from Teensy references (e.g., A0, A1, etc.) to the Kinetis analog pin numbers using the adc.CHANNEL_TO_SC1A_ADC0 mapping.

In [7]:
import re

import numpy as np
import pandas as pd
import arduino_helpers.hardware.teensy.adc as adc


sc1a_pins = pd.Series(dict([(v, adc.CHANNEL_TO_SC1A_ADC0[getattr(teensy, v)]) for v in dir(teensy) if re.search(r'^A\d+', v)]))
channel_sc1as = np.array(sc1a_pins[['A0', 'A1', 'A0', 'A3', 'A0']].tolist(), dtype='uint32')

Allocate and initialize device arrays

  • SD1A register configuration for each ADC channel in the channel_sc1as list.
    • Copy channel_sc1as list to device.
  • ADC result array
    • Initialize to zero.

In [8]:
proxy.free_all()

N = np.dtype('uint16').itemsize * channel_sc1as.size

# Allocate source array
adc_result_addr = proxy.mem_alloc(N)

# Fill result array with zeros
proxy.mem_fill_uint8(adc_result_addr, 0, N)

# Copy channel SC1A configurations to device memory
adc_sda1s_addr = proxy.mem_aligned_alloc_and_set(4, channel_sc1as.view('uint8'))

print 'ADC results:', proxy.mem_cpy_device_to_host(adc_result_addr, N).view('uint16')
print 'Analog pins:', proxy.mem_cpy_device_to_host(adc_sda1s_addr, len(channel_sc1as) *
                                                   channel_sc1as.dtype.itemsize).view('uint32')


ADC results: [0 0 0 0 0]
Analog pins: [ 5 14  5  9  5]

Configure DMA channel $i$


In [9]:
ADC0_SC1A = 0x4003B000  # ADC status and control registers 1

sda1_tcd_msg = DMA.TCD(CITER_ELINKNO=DMA.R_TCD_ITER_ELINKNO(ELINK=False, ITER=channel_sc1as.size),
                       BITER_ELINKNO=DMA.R_TCD_ITER_ELINKNO(ELINK=False, ITER=channel_sc1as.size),
                       ATTR=DMA.R_TCD_ATTR(SSIZE=DMA.R_TCD_ATTR._32_BIT,
                                           DSIZE=DMA.R_TCD_ATTR._32_BIT),
                       NBYTES_MLNO=4,
                       SADDR=int(adc_sda1s_addr),
                       SOFF=4,
                       SLAST=-channel_sc1as.size * 4,
                       DADDR=int(ADC0_SC1A),
                       DOFF=0,
                       DLASTSGA=0,
                       CSR=DMA.R_TCD_CSR(START=0, DONE=False))

proxy.update_dma_TCD(1, sda1_tcd_msg)


Out[9]:
0

Configure DMA channel $ii$


In [10]:
ADC0_RA = 0x4003B010  # ADC data result register
ADC0_RB = 0x4003B014  # ADC data result register


tcd_msg = DMA.TCD(CITER_ELINKYES=DMA.R_TCD_ITER_ELINKYES(ELINK=True, LINKCH=1, ITER=channel_sc1as.size),
                  BITER_ELINKYES=DMA.R_TCD_ITER_ELINKYES(ELINK=True, LINKCH=1, ITER=channel_sc1as.size),
                  ATTR=DMA.R_TCD_ATTR(SSIZE=DMA.R_TCD_ATTR._16_BIT,
                                      DSIZE=DMA.R_TCD_ATTR._16_BIT),
                  NBYTES_MLNO=2,
                  SADDR=ADC0_RA,
                  SOFF=0,
                  SLAST=0,
                  DADDR=int(adc_result_addr),
                  DOFF=2,
                  DLASTSGA=-channel_sc1as.size * 2,
                  CSR=DMA.R_TCD_CSR(START=0, DONE=False))

proxy.update_dma_TCD(0, tcd_msg)


Out[10]:
0

Trigger sample scan across selected ADC channels


In [11]:
# Clear output array to zero.
proxy.mem_fill_uint8(adc_result_addr, 0, N)

# Software trigger channel $i$ to copy *first* SC1A configuration, which
# starts ADC conversion for the first channel.
#
# Conversions for subsequent ADC channels are triggered through minor-loop
# linking from DMA channel $ii$ to DMA channel $i$ (*not* through explicit
# software trigger).
proxy.update_dma_registers(DMA.Registers(SSRT=1))

# Display converted ADC values (one value per channel in `channel_sd1as` list).
print 'ADC results:', proxy.mem_cpy_device_to_host(adc_result_addr, N).view('uint16')


ADC results: [65525     2 65535 29006 65535]

In [ ]: